﻿using SoldiHive.Core.Cache;
using SoldiHive.Core.Crawlers;
using SoldiHive.Core.Persistence;
using SoldiHive.Infrustructure.Extensions;
using SoldiHive.Model.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SoldiHive.Core
{
	public class MarketViewer: IMarketViewer
	{
		private IEarningsStatementsCrawler earningsStatementsCrawler;
		private ICurrentPriceCrawler currentPriceCrawler;
		private IRatingsCrawler ratingsCrawler;
		private IQuotesCrawler quotesCrawler;
		private IRivalsCrawler rivalsCrawler;
		private ICompanyRepository companyRepository;
		private ICache cache;
		private IMarketViewerConfiguration configuration;

		public MarketViewer(IEarningsStatementsCrawler earningsStatementsCrawler, ICurrentPriceCrawler currentPriceCrawler, IRatingsCrawler ratingsCrawler, IQuotesCrawler quotesCrawler, IRivalsCrawler rivalsCrawler, ICompanyRepository companyRepository, ICache cache, IMarketViewerConfiguration marketViewerConfiguration)
		{
			this.earningsStatementsCrawler = earningsStatementsCrawler;
			this.currentPriceCrawler = currentPriceCrawler;
			this.ratingsCrawler = ratingsCrawler;
			this.quotesCrawler = quotesCrawler;
			this.rivalsCrawler = rivalsCrawler;
			this.companyRepository = companyRepository;
			this.cache = cache;
			this.configuration = marketViewerConfiguration;
		}

		public IList<CalendarView> GetCalendarViews(DateTime start, DateTime end)
		{
			var cacheKey = this.cache.Key("GetCalendarViews", start.ToString("ddMMyyy"), end.ToString("ddMMyyy"));
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<IList<CalendarView>>(cacheKey);
			}

			var bussinessDates = start.BusinessDatesBefore(end);
			var earningsStatements = this.earningsStatementsCrawler.GetEarningsStatements(bussinessDates);
			earningsStatements = earningsStatements.Where(earningsStatement => !this.configuration.OnlyPlus500Companies || this.configuration.Plus500Companies.Any(plus500CompanyTicker => plus500CompanyTicker.Equals(earningsStatement.Company.Ticker, StringComparison.InvariantCultureIgnoreCase))).ToList();
			earningsStatements = earningsStatements.Where(earningsStatement => earningsStatement.Company.Cap >= this.configuration.MinCompanyCapThreshold).ToList();

			var calendarViews = new List<CalendarView>();
			Parallel.ForEach(earningsStatements, (earningsStatement) =>
			{
				var ratings = this.ratingsCrawler.GetRatings(earningsStatement.Company);
				var currentPrice = this.currentPriceCrawler.GetCurrentPrice(earningsStatement.Company);
				var calendarView = new CalendarView(earningsStatement, currentPrice, ratings);

				calendarViews.Add(calendarView);
			});

			calendarViews = calendarViews
				.Where(view => view.AvarageRating >= this.configuration.MinRatingThreshold)
				.Where(view => view.Ratings.All(rating => rating.Rate >= this.configuration.MinAbsoluteRatingThreshold))
				.OrderBy(view => view.AvarageRating)
				.Reverse()
				.ToList();

			if (this.cache.Enabled && calendarViews.Any())
			{
				this.cache.Put(cacheKey, calendarViews, this.configuration.defaultCacheLifeSpanDays);
			}

			return calendarViews;
		}

		public IList<ResponseView> GetResponseViews(string ticker)
		{
			var cacheKey = this.cache.Key("GetResponseViews", ticker);
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<IList<ResponseView>>(cacheKey);
			}

			var company = this.companyRepository.GetCompany(ticker);
			var pastEarningsStatements = this.earningsStatementsCrawler.GetPastEarningsStatements(company);

			var responseViews = new List<ResponseView>();

			Parallel.ForEach(pastEarningsStatements, (pastEarningsStatement) =>
			{
				var neighbouringQuotes = this.quotesCrawler.GetNeighbouringQuotes(pastEarningsStatement.Company, pastEarningsStatement.Date);
				var responseView = new ResponseView(pastEarningsStatement, neighbouringQuotes);

				responseViews.Add(responseView);
			});

			responseViews = responseViews
				.OrderBy(view => view.PastEarningsStatement.Date)
				.Reverse()
				.ToList();

			if (this.cache.Enabled && responseViews.Any())
			{
				this.cache.Put(cacheKey, responseViews, this.configuration.defaultCacheLifeSpanDays);
			}

			return responseViews;
		}

		public IList<SectorView> GetSectorViews(string ticker, DateTime start, DateTime end)
		{
			var cacheKey = this.cache.Key("GetSectorViews", ticker, start.ToString("ddMMyyy"), end.ToString("ddMMyyy"));
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<IList<SectorView>>(cacheKey);
			}

			const int rivalsCount = 10;

			var company = this.companyRepository.GetCompany(ticker);

			if (company == null)
			{
				return Enumerable.Empty<SectorView>().ToList();
			}

			var rivals = this.rivalsCrawler.GetRivals(company)
				.OrderBy(s => s.Cap)
				.Reverse()
				.Take(rivalsCount);

			start = start.GetClosestBusinessDate();
			end = end.GetClosestBusinessDate();

			var sectorViews = new List<SectorView>();
			Parallel.ForEach(rivals, (rival) =>
			{
				var startQuote = this.quotesCrawler.GetQuote(rival, start);
				var endQuote = this.quotesCrawler.GetQuote(rival, end);
				var ratings = this.ratingsCrawler.GetRatings(rival);

				var sectorView = new SectorView(rival, startQuote, endQuote, ratings);
				sectorViews.Add(sectorView);
			});

			sectorViews = sectorViews.OrderBy(view => view.EndQuote.Close - view.StartQuote.Open).ToList();

			if (this.cache.Enabled && sectorViews.Any())
			{
				this.cache.Put(cacheKey, sectorViews, this.configuration.defaultCacheLifeSpanDays);
			}

			return sectorViews;
		}

		public ScheduleView GetScheduleView(string ticker)
		{
			var cacheKey = this.cache.Key("GetScheduleView", ticker);
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<ScheduleView>(cacheKey);
			}

			var company = this.companyRepository.GetCompany(ticker);
			var rating = this.ratingsCrawler.GetRatings(company);
			var currentPrice = this.currentPriceCrawler.GetCurrentPrice(company);
			var earningsStatement = this.earningsStatementsCrawler.GetEarningsStatement(company);

			var scheduleView = new ScheduleView(earningsStatement, currentPrice, rating);

			if (this.cache.Enabled && scheduleView != null)
			{
				this.cache.Put(cacheKey, scheduleView, this.configuration.defaultCacheLifeSpanDays);
			}

			return scheduleView;
		}

		public RatingsView GetRatingsView(string ticker)
		{
			var cacheKey = this.cache.Key("GetRatingsView", ticker);
			if (this.cache.Enabled && this.cache.Has(cacheKey))
			{
				return this.cache.Get<RatingsView>(cacheKey);
			}

			var company = this.companyRepository.GetCompany(ticker);
			var ratings = this.ratingsCrawler.GetRatings(company);
			var currentPrice = this.currentPriceCrawler.GetCurrentPrice(company);

			var ratingsView = new RatingsView(company, currentPrice, ratings);

			if (this.cache.Enabled && ratingsView != null)
			{
				this.cache.Put(cacheKey, ratingsView, this.configuration.defaultCacheLifeSpanDays);
			}

			return ratingsView;
		}
	}
}